// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of 'internal.dart';

/// Per-message type setup.
class BuilderInfo {
  /// Fully qualified name of this message.
  final String qualifiedMessageName;

  /// Mapping from [FieldInfo.index]s to [FieldInfo]s.
  final List<FieldInfo> byIndex = <FieldInfo>[];

  /// Mapping from [FieldInfo.tagNumber]s to [FieldInfo]s.
  final Map<int, FieldInfo> fieldInfo = <int, FieldInfo>{};

  /// Mapping from string representation of [FieldInfo.tagNumber]s to
  /// [FieldInfo]s.
  ///
  /// This map is used when parsing JSON messages generated by
  /// [GeneratedMessage.writeToJson], which converts field tags to strings and
  /// uses those strings as JSON map keys. With this field we avoid parsing
  /// those strings to integers when parsing the generated maps.
  final Map<String, FieldInfo> byTagAsString = <String, FieldInfo>{};

  /// Mapping from [FieldInfo.name]s to [FieldInfo]s.
  final Map<String, FieldInfo> byName = <String, FieldInfo>{};

  /// Mapping from `oneof` field [FieldInfo.tagNumber]s to the their indices in
  /// [FieldSet._oneofCases].
  final Map<int, int> oneofs = <int, int>{};

  /// Whether the message has extension fields.
  bool hasExtensions = false;

  /// Whether the message has required fields.
  ///
  /// Note that proto3 doesn't have required fields, only proto2 does.
  bool hasRequiredFields = true;

  List<FieldInfo>? _sortedByTag;

  // For well-known types.
  final WellKnownType? _wellKnownType;

  final CreateBuilderFunc? createEmptyInstance;

  BuilderInfo(
    String? messageName, {
    PackageName package = const PackageName(''),
    this.createEmptyInstance,
    WellKnownType? wellKnownType,
  }) : qualifiedMessageName = '${package.prefix}$messageName',
       _wellKnownType = wellKnownType;

  void add<T>(
    int tagNumber,
    String name,
    int? fieldType,
    dynamic defaultOrMaker,
    CreateBuilderFunc? subBuilder,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues, {
    String? protoName,
  }) {
    if (tagNumber == 0) {
      addUnused();
    } else {
      final index = byIndex.length;
      final fieldInfo = FieldInfo<T>(
        name,
        tagNumber,
        index,
        fieldType!,
        defaultOrMaker: defaultOrMaker,
        subBuilder: subBuilder,
        valueOf: valueOf,
        enumValues: enumValues,
        protoName: protoName,
      );
      _addField(fieldInfo);
    }
  }

  // Support for tree-shaking of unused fields.
  void addUnused() {
    final index = byIndex.length;
    _addField(FieldInfo.dummy(index));
  }

  void addMapField<K, V>(
    int tagNumber,
    String name,
    int keyFieldType,
    int valueFieldType,
    BuilderInfo mapEntryBuilderInfo,
    CreateBuilderFunc? valueCreator, {
    ProtobufEnum? defaultEnumValue,
    String? protoName,
  }) {
    final index = byIndex.length;
    _addField(
      MapFieldInfo<K, V>(
        name,
        tagNumber,
        index,
        PbFieldType.M,
        keyFieldType,
        valueFieldType,
        mapEntryBuilderInfo,
        valueCreator,
        defaultEnumValue: defaultEnumValue,
        protoName: protoName,
      ),
    );
  }

  void addRepeated<T>(
    int tagNumber,
    String name,
    int fieldType,
    CheckFunc<T> check,
    CreateBuilderFunc? subBuilder,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues, {
    ProtobufEnum? defaultEnumValue,
    String? protoName,
  }) {
    final index = byIndex.length;
    _addField(
      FieldInfo<T>.repeated(
        name,
        tagNumber,
        index,
        fieldType,
        check,
        subBuilder,
        valueOf: valueOf,
        enumValues: enumValues,
        defaultEnumValue: defaultEnumValue,
        protoName: protoName,
      ),
    );
  }

  void _addField(FieldInfo fi) {
    byIndex.add(fi);
    assert(byIndex[fi.index!] == fi);
    // Fields with tag number 0 are considered dummy fields added to avoid
    // index calculations add up. They should not be reflected in the following
    // maps.
    if (!fi._isDummy) {
      fieldInfo[fi.tagNumber] = fi;
      byTagAsString['${fi.tagNumber}'] = fi;
      byName[fi.name] = fi;
    }
  }

  void a<T>(
    int tagNumber,
    String name,
    int fieldType, {
    dynamic defaultOrMaker,
    CreateBuilderFunc? subBuilder,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues,
    String? protoName,
  }) {
    add<T>(
      tagNumber,
      name,
      fieldType,
      defaultOrMaker,
      subBuilder,
      valueOf,
      enumValues,
      protoName: protoName,
    );
  }

  /// Adds PbFieldType.OS String with no default value to reduce generated
  /// code size.
  void aOS(int tagNumber, String name, {String? protoName}) {
    add<String>(
      tagNumber,
      name,
      PbFieldType.OS,
      null,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds PbFieldType.PS String with no default value.
  void pPS(int tagNumber, String name, {String? protoName}) {
    addRepeated<String>(
      tagNumber,
      name,
      PbFieldType.PS,
      getCheckFunction(PbFieldType.PS),
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds PbFieldType.QS String with no default value.
  void aQS(int tagNumber, String name, {String? protoName}) {
    add<String>(
      tagNumber,
      name,
      PbFieldType.QS,
      null,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds Int64 field with Int64.ZERO default.
  void aInt64(int tagNumber, String name, {String? protoName}) {
    add<Int64>(
      tagNumber,
      name,
      PbFieldType.O6,
      Int64.ZERO,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds a boolean with no default value.
  void aOB(int tagNumber, String name, {String? protoName}) {
    add<bool>(
      tagNumber,
      name,
      PbFieldType.OB,
      null,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds a double field.
  void aD(
    int tagNumber,
    String name, {
    int fieldType = PbFieldType.OD,
    dynamic defaultOrMaker,
    String? protoName,
  }) {
    add<double>(
      tagNumber,
      name,
      fieldType,
      defaultOrMaker,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  /// Adds an int field.
  void aI(
    int tagNumber,
    String name, {
    int fieldType = PbFieldType.O3,
    dynamic defaultOrMaker,
    String? protoName,
  }) {
    add<int>(
      tagNumber,
      name,
      fieldType,
      defaultOrMaker,
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  // Enum.
  void e<T>(
    int tagNumber,
    String name,
    int fieldType, {
    dynamic defaultOrMaker,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues,
    String? protoName,
  }) {
    add<T>(
      tagNumber,
      name,
      fieldType,
      defaultOrMaker,
      null,
      valueOf,
      enumValues,
      protoName: protoName,
    );
  }

  // Enum, updated version.
  void aE<E extends ProtobufEnum>(
    int tagNumber,
    String name, {
    int fieldType = PbFieldType.OE,
    dynamic defaultOrMaker,
    ValueOfFunc? valueOf,
    required List<E> enumValues,
    String? protoName,
  }) {
    defaultOrMaker ??= enumValues.first;
    valueOf ??= _findValueOfEnumFunction<E>(enumValues);
    add<E>(
      tagNumber,
      name,
      fieldType,
      defaultOrMaker,
      null,
      valueOf,
      enumValues,
      protoName: protoName,
    );
  }

  // Repeated, not a message, group, or enum.
  void p<T>(int tagNumber, String name, int fieldType, {String? protoName}) {
    assert(
      !PbFieldType.isGroupOrMessage(fieldType) &&
          !PbFieldType.isEnum(fieldType),
    );
    addRepeated<T>(
      tagNumber,
      name,
      fieldType,
      getCheckFunction(fieldType),
      null,
      null,
      null,
      protoName: protoName,
    );
  }

  // Repeated message, group, or enum.
  void pc<T>(
    int tagNumber,
    String name,
    int fieldType, {
    CreateBuilderFunc? subBuilder,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues,
    ProtobufEnum? defaultEnumValue,
    String? protoName,
  }) {
    assert(
      PbFieldType.isGroupOrMessage(fieldType) || PbFieldType.isEnum(fieldType),
    );
    addRepeated<T>(
      tagNumber,
      name,
      fieldType,
      _checkNotNull,
      subBuilder,
      valueOf,
      enumValues,
      defaultEnumValue: defaultEnumValue,
      protoName: protoName,
    );
  }

  // Repeated enum.
  void pPE<E extends ProtobufEnum>(
    int tagNumber,
    String name, {
    int fieldType = PbFieldType.PE,
    ValueOfFunc? valueOf,
    required List<E> enumValues,
    ProtobufEnum? defaultEnumValue,
    String? protoName,
  }) {
    assert(PbFieldType.isEnum(fieldType));
    defaultEnumValue ??= enumValues.first;
    valueOf ??= _findValueOfEnumFunction<E>(enumValues);
    addRepeated<E>(
      tagNumber,
      name,
      fieldType,
      _checkNotNull,
      null,
      valueOf,
      enumValues,
      defaultEnumValue: defaultEnumValue,
      protoName: protoName,
    );
  }

  // Optional Message.
  void aOM<T extends GeneratedMessage>(
    int tagNumber,
    String name, {
    required T Function() subBuilder,
    String? protoName,
  }) {
    add<T>(
      tagNumber,
      name,
      PbFieldType.OM,
      GeneratedMessage._defaultMakerFor<T>(subBuilder),
      subBuilder,
      null,
      null,
      protoName: protoName,
    );
  }

  // reQuried Message.
  void aQM<T extends GeneratedMessage>(
    int tagNumber,
    String name, {
    required T Function() subBuilder,
    String? protoName,
  }) {
    add<T>(
      tagNumber,
      name,
      PbFieldType.QM,
      GeneratedMessage._defaultMakerFor<T>(subBuilder),
      subBuilder,
      null,
      null,
      protoName: protoName,
    );
  }

  // rePeated Message, specialization of pc<T>.
  void pPM<T extends GeneratedMessage>(
    int tagNumber,
    String name, {
    required T Function() subBuilder,
    String? protoName,
  }) {
    addRepeated<T>(
      tagNumber,
      name,
      PbFieldType.PM,
      _checkNotNull,
      subBuilder,
      null,
      null,
      protoName: protoName,
    );
  }

  // oneof declarations.
  void oo(int oneofIndex, List<int> tags) {
    for (final tag in tags) {
      oneofs[tag] = oneofIndex;
    }
  }

  // Map field.
  void m<K, V>(
    int tagNumber,
    String name, {
    String? entryClassName,
    required int keyFieldType,
    required int valueFieldType,
    CreateBuilderFunc? valueCreator,
    ValueOfFunc? valueOf,
    List<ProtobufEnum>? enumValues,
    ProtobufEnum? defaultEnumValue,
    PackageName packageName = const PackageName(''),
    String? protoName,
    dynamic valueDefaultOrMaker,
  }) {
    final mapEntryBuilderInfo =
        BuilderInfo(entryClassName, package: packageName)
          ..add(mapKeyFieldNumber, 'key', keyFieldType, null, null, null, null)
          ..add(
            mapValueFieldNumber,
            'value',
            valueFieldType,
            valueDefaultOrMaker,
            valueCreator,
            valueOf,
            enumValues,
          );

    addMapField<K, V>(
      tagNumber,
      name,
      keyFieldType,
      valueFieldType,
      mapEntryBuilderInfo,
      valueCreator,
      defaultEnumValue: defaultEnumValue,
      protoName: protoName,
    );
  }

  bool containsTagNumber(int tagNumber) => fieldInfo.containsKey(tagNumber);

  dynamic defaultValue(int tagNumber) {
    final func = makeDefault(tagNumber);
    return func == null ? null : func();
  }

  // Returns the field name for a given tag number, for debugging purposes.
  String? fieldName(int tagNumber) {
    final i = fieldInfo[tagNumber];
    return i?.name;
  }

  int? fieldType(int tagNumber) {
    final i = fieldInfo[tagNumber];
    return i?.type;
  }

  MakeDefaultFunc? makeDefault(int tagNumber) {
    final i = fieldInfo[tagNumber];
    return i?.makeDefault;
  }

  CreateBuilderFunc? subBuilder(int tagNumber) {
    final i = fieldInfo[tagNumber];
    return i?.subBuilder;
  }

  int? tagNumber(String fieldName) {
    final i = byName[fieldName];
    return i?.tagNumber;
  }

  ValueOfFunc? valueOfFunc(int tagNumber) {
    final i = fieldInfo[tagNumber];
    return i?.valueOf;
  }

  /// The [FieldInfo] for each field in tag number order.
  List<FieldInfo> get sortedByTag => _sortedByTag ??= _computeSortedByTag();

  /// The message name. Also see [qualifiedMessageName].
  String get messageName {
    final lastDot = qualifiedMessageName.lastIndexOf('.');
    return lastDot == -1
        ? qualifiedMessageName
        : qualifiedMessageName.substring(lastDot + 1);
  }

  List<FieldInfo> _computeSortedByTag() =>
  // Code generator inserts fields in tag order, but it's possible for
  // user-written code to insert unordered.
  List<FieldInfo>.from(fieldInfo.values, growable: false)
    ..sort((FieldInfo a, FieldInfo b) => a.tagNumber.compareTo(b.tagNumber));

  GeneratedMessage _makeEmptyMessage(
    int tagNumber,
    ExtensionRegistry? extensionRegistry,
  ) {
    var subBuilderFunc = subBuilder(tagNumber);
    if (subBuilderFunc == null && extensionRegistry != null) {
      subBuilderFunc =
          extensionRegistry
              .getExtension(qualifiedMessageName, tagNumber)!
              .subBuilder;
    }
    return subBuilderFunc!();
  }

  ProtobufEnum? _decodeEnum(
    int tagNumber,
    ExtensionRegistry? registry,
    int rawValue,
  ) {
    final f = valueOfFunc(tagNumber);
    if (f != null) {
      return f(rawValue);
    }
    return registry
        ?.getExtension(qualifiedMessageName, tagNumber)
        ?.valueOf
        ?.call(rawValue);
  }
}

extension BuilderInfoInternalExtension on BuilderInfo {
  GeneratedMessage makeEmptyMessage(
    int tagNumber,
    ExtensionRegistry? extensionRegistry,
  ) => _makeEmptyMessage(tagNumber, extensionRegistry);

  ProtobufEnum? decodeEnum(
    int tagNumber,
    ExtensionRegistry? registry,
    int rawValue,
  ) => _decodeEnum(tagNumber, registry, rawValue);
}

E? Function(int) _findValueOfEnumFunction<E extends ProtobufEnum>(
  List<E> enumValues,
) {
  final function = _valueOfFunctions[enumValues];
  if (function != null) {
    // 'as dynamic' causes an implicit downcast to `E? Function(int)`, which is
    // removed by dart2js at `-O3`.
    return function as dynamic;
  }
  return _valueOfFunctions[enumValues] = _makeValueOfEnumFunction<E>(
    enumValues,
  );
}

/// Map for finding 'valueOf' functions for enums. An identity map is used since
/// the `const` 'values' list for the enum is canonicalized and distinct for
/// each enum.
final _valueOfFunctions =
    Map<List<ProtobufEnum>, ProtobufEnum? Function(int)>.identity();

E? Function(int) _makeValueOfEnumFunction<E extends ProtobufEnum>(
  List<E> values,
) {
  Map<int, E>? map;

  E? intToEnumValue(int value) {
    return (map ??= ProtobufEnum.initByValue(values))[value];
  }

  return intToEnumValue;
}
